/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/commercial
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may use this file in compliance with the License.
 * Alternatively, if you can't copy with the License, namely if you do not
 * want to provide source code of your modifications to this file, you may
 * negotiate a commercial licence with the initial developer.
 * You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Brano Kemen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "coid/comm/binstream/netstream.h"
#include "coid/comm/binstream/textstream.h"
#include "coid/comm/binstream/filestream.h"
#include "coid/comm/binstream/binstreambuf.h"
#include "coid/comm/tokenizer.h"
#include "coid/comm/dir.h"

#include "coid/svc/coidsvc.h"

#include "server.h"
#include "acceptor.h"
//#include "tower.h"
#include "coid/coid.h"
#include "plugin.h"

#include <algorithm>
#include <sys/stat.h>
//#include <stdio.h>
//#include <string.h>
//#include <io.h>
#include <signal.h>
//#include <windows.h>

#ifdef SYSTYPE_MSVC
#include <direct.h>
#endif

namespace coid {

stdchartokenizer Gtokenizer;
pathchartokenizer Gpathtokenizer;
cmdchartokenizer Gcmdtokenizer;

version  GserverVersion(COID_VERSION);


//fwd
extern void get_COID_register_AccountMgr();
extern void get_COID_register_LogEntMgr();

void logstream_ext::flush()
{
    SRVROOTA._log.set_entry(this);
}

////////////////////////////////////////////////////////////////////////////////
extern "C" {
int coid_server_start( int& port, int (*cancel_cbk)(int) )
{
    return 0 != SRVROOT.init( cancel_cbk, port );
}

int coid_server_stop()
{
    SRVROOT.stop();
    return 0;
}

int coid_get_version( charstr & buf )
{
	GserverVersion.get_version( buf, true );
    return 0;
}
} //extern "C"

////////////////////////////////////////////////////////////////////////////////
void ServerGlobal::coid_sig_handler( int sig )
{
#ifdef SYSTYPE_LINUX
/*    
    if( sig == SIGINT )
        SRVROOT.post_server_msg(LOGMSG::TYPE_ALERT,0) << "SIGINT received" << EOM;
    if( sig == SIGABRT )
        SRVROOT.post_server_msg(LOGMSG::TYPE_ALERT,0) << "SIGABRT received" << EOM;
    if( sig == SIGSEGV )
        SRVROOT.post_server_msg(LOGMSG::TYPE_ALERT,0) << "SIGSEGV received" << EOM;
    if( sig == SIGILL )
        SRVROOT.post_server_msg(LOGMSG::TYPE_ALERT,0) << "SIGILL received" << EOM;
    if( sig == SIGFPE )
        SRVROOT.post_server_msg(LOGMSG::TYPE_ALERT,0) << "SIGFPE received" << EOM;
*/
    //throw thread::ExitException();
    //pthread_exit();
#endif
}

////////////////////////////////////////////////////////////////////////////////
ServerGlobal::ServerGlobal()
{
    directory::get_cwd( _cwd );

    //force load plugins
    //get_COID_register_AccountMgr();
    //get_COID_register_LogEntMgr();

    _being_destroyed = false;
    _run_updater = 1;

    _accpcoid_nodeid = UMAX32;
    _coidaccport = 0;
    _cancel_cbk = 0;


#ifdef SYSTYPE_LINUX
/*
    signal( SIGINT, coid_sig_handler );
    signal( SIGABRT, coid_sig_handler );
    signal( SIGSEGV, coid_sig_handler );
    signal( SIGILL, coid_sig_handler );
    signal( SIGFPE, coid_sig_handler );

    signal( SIGPIPE, SIG_IGN );*/
#endif

	//_deadlist_mutex.set_name( "ServerGlobal::_deadlist_mutex" );
	//_deadlist_mutex.set_objid( 0 );
	_mutex.set_name( "ServerGlobal::_mutex" );
	_mutex.set_objid( 0 );
}

////////////////////////////////////////////////////////////////////////////////
opcd ServerGlobal::start_acceptor( NODEREF<ServiceCoid>& svc, int port )
{
    if( svc->_desc._flags & ServiceDescriptor::fACCEPTOR_BOUND_SESSION )
    {
        svc->post_info(0) << "starting bound acceptor on port " << port << EOM;
        local<UniBoundAcceptorCoid> acc = new UniBoundAcceptorCoid;

        NODEREF<UniBoundAcceptorCoid> pacc;
        opcd e = SRVROOT.attach( acc.ptr(), pacc, 0, 0 );

        if(e)
            return e;

        acc.eject();

        e = pacc->init( port, svc );
        if(e) return e;

        if( pacc->spawn() )
        {
            pacc.unset();
            return 0;
        }
    }
    else
    {
        svc->post_info(0) << "starting acceptor on port " << port << EOM;
        local<UniAcceptorCoid> acc = new UniAcceptorCoid;

        NODEREF<UniAcceptorCoid> pacc;
        opcd e = SRVROOT.attach( acc.ptr(), pacc, 0, 0 );
        if(e)  return e;

        acc.eject();

        e = pacc->init( port, svc );
        if(e) return e;

        if( pacc->spawn() )
        {
            pacc.unset();
            return 0;
        }
    }

    return ersUNAVAILABLE;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServerGlobal::start_coid_acceptor( int& port )
{
    NODEREF<PluginScannerCoid> pssc;
    attach( new PluginScannerCoid(port), pssc, 0, 0 );

    opcd e;
    e = pssc->load_cfg(true);
    if(e) {
        pssc->post_error(0) << "error loading configuration file: " << opcd_formatter(e) << BINSTREAM_FLUSH;
        return e;
    }

    //port may have changed
    int p = pssc->get_port();
    if( p != port )
    {
        pssc->post_info(0) << "coid acceptor port changed by dev.conf to " << p << EOM;
        port = p;
    }

    NODEREF<CoidAcceptorCoid> pacc;
    attach( new CoidAcceptorCoid, pacc, 0, 0 );

    //initialize the main acceptor
    try {
        e = pacc->init(port);
        if(e) return e;
    }
    catch( opcd err )
    {
        pacc->post_critical(0) << "exception during the initialization of main coid acceptor object: "
            << opcd_formatter(err) << EOM;
        return err;
    }

    //initialize the plugin scanner
    try {
        e = pssc->init();
        if(e)  return e;
    }
    catch( opcd err )
    {
        pacc->post_critical(0) << "exception during the initialization of plugin scanner object: "
            << opcd_formatter(err) << EOM;
        return err;
    }

    pacc->post_trace(1) << "resolving server address .." << EOM;

    _coidaccport = (ushort) pacc->get_id();
    pacc->get_server_address( &_coidaddr );

    charstr buf;
    pacc->post_trace(1) << "server address: " << _coidaddr.getHost(buf,true) << EOM;


    pacc->get_server_address( _coidaddrstring );
    pacc->post_trace(1) << "server name: " << _coidaddrstring << EOM;


    if( pacc->spawn() )
    {
        _accpcoid_nodeid = pacc->get_id();

		pssc->spawn_update_plugin_list();
		pssc.unset();

        pacc.unset();
        return 0;
    }

    return ersUNAVAILABLE;
}
/*
////////////////////////////////////////////////////////////////////////////////
opcd ServerGlobal::start_tower_acceptor( int port )
{
    NODEREF<TowerCoid> tacc;

    opcd e = attach( new TowerCoid, tacc, 0, 0 );

    if(!e)
        e = tacc->init(port);

    if( !e  &&  tacc->spawn() )
    {
        tacc.unset();
        return 0;
    }

    return e;
}
*/
////////////////////////////////////////////////////////////////////////////////
//
opcd ServerGlobal::init( int (*cancel_cbk)(int), int& coidport )
{

#ifndef SYSTYPE_WIN
    setpgrp();
#endif

    _cancel_cbk = cancel_cbk;

	SINGLETON(HPTIMER).reset();	/// init HPTIMER singleton
    AcceptorCoid::init_pid( sysGetPid() );

    netSubsystem::instance();

    NODEREF<RootCoid> rc;
    opcd e = attach( new RootCoid, rc, UMAX32, 0 );
    if(e)  return e;

    rc->spawn_logdump();
    rc->spawn_graveyard_loop();

    charstr buf;
    rc->post_info(0) << "COID version " << GserverVersion.get_version(buf, true) << EOM;

    rc.unset();

    e = try_start_coid_acceptor( coidport );
    if(e)
    { sysSleep(1); return e; }

    sysSleep(1);

    //password pwd;
    //pwd.hash("");
    e = _accmgr.connect_shared( 0, get_coidaccpstring().ptr() );


    //start_tower_acceptor( TowerCoid::PORT );


    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServerGlobal::try_start_coid_acceptor( int& coidport )
{
    opcd e;
    int cnt=0;

    while( ersFAILED == (e = start_coid_acceptor(coidport)) )
    {
        if( 0 == cnt++ )
            post_server_msg(LOGMSG::TYPE_ALERT,0) << "cannot initialize acceptor on port " << coidport
            << ", waiting" << EOM;
        sysSleep(1);
    }

    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServerGlobal::stop()
{
    _being_destroyed = true;

    _accmgr.disconnect();

    detach(0,0);

    uints undead;
    while( (undead = _objmgr.detach_dead()) > 0 )
        sysMilliSecondSleep(100);

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
void ServerGlobal::prune_dead()
{
    _objmgr.detach_dead();
}

////////////////////////////////////////////////////////////////////////////////
opcd ServerGlobal::detach( uint oid, uchar verb, bool children_only )
{
    opcd e = _objmgr.request_detach( oid, children_only );
    if(e)
    {
        post_server_msg( LOGMSG::TYPE_ERROR, verb ) << "error detaching object id " << oid << ": "
            << opcd_formatter(e) << EOM;
    }

    unregister_autodestroy(oid);
    return e;
}

////////////////////////////////////////////////////////////////////////////////
///Parse object path
/**
    Path consist of multiple path elements separated by '/' character.
    Path element may be either number specifying object id or a name identifying class and/or name
    of the object, whatever is sufficient to identify right object. The name has format
        <name>.<class>
    where <name> is the name set for the object, and <class> is object's class.

    Leading / denotes the root object. Absolute path starts with slash, for example
        /boss/admin.bcon/.tty
    specifies all objects of class 'tty' under all objects of class 'bcon' named 'admin', that occur under
    object named 'boss' hooked to the root.

    .  denotes current device
    .. specifies parent device to the current device

    @param path path to parse
    @param out result set, id's of objects that match the criteria
*/
opcd ServerGlobal::parse_path( LNID idc, LNID idh, const token& path, dynarray<uint>& out, bool firstturn ) const
{
    if( path.is_empty() )
    {
        *out.add(1) = idc;
        return 0;
    }

    token tok;
    tok.set_empty(path.ptr());

    Gpathtokenizer.next(tok);

    if (tok.is_empty()  ||  Gpathtokenizer.in_group( tok[0], Gpathtokenizer.OPERATORS) )
    {
        *out.add (1) = idc;
        return 0;
    }

    uint n;
    bool do_scan = false;

    n = idc;
    if( tok == '~' )
    {
        if(!firstturn)
            return ersSYNTAX_ERROR;
        n = idh;
        Gpathtokenizer.next(tok);
    }
    if( tok == '/' )
    {
        if (firstturn)  n = 0;
        else  n = idc;
        Gpathtokenizer.next(tok);
    }
    else if( tok == '@' )
    {
        token t2 = tok;
        Gpathtokenizer.next (t2);
        if (t2.is_empty()  ||  Gpathtokenizer.in_group (t2[0], Gpathtokenizer.OPERATORS)) {
            *out.add(1) = n;
            return 0;
        }
        if (t2 == '/') {
            tok = t2;
            Gpathtokenizer.next(tok);
        }
    }
    else if( tok == '*' )
    {
        do_scan = true;
        Gpathtokenizer.next (tok);
    }
    else if( Gpathtokenizer.in_group(tok[0], Gpathtokenizer.NUMERIC) )
    {
        if (!firstturn)  return ersSYNTAX_ERROR;
        n = tok.touint();
        Gpathtokenizer.next (tok);
        if (tok.is_empty()  ||  Gpathtokenizer.in_group (tok[0], Gpathtokenizer.OPERATORS)) {
            *out.add(1) = n;
            return 0;
        }
        if (tok == '/')
            Gpathtokenizer.next(tok);
        else if (tok == '*') {
            do_scan = true;
            Gpathtokenizer.next(tok);
        }
        else if (tok[0] != '.')  return ersSYNTAX_ERROR;
    }
    else if (!Gpathtokenizer.in_group (tok[0], Gpathtokenizer.ALPHANUMERIC))
        return ersSYNTAX_ERROR;

    //NODEREF<CoidNode> id;
    //opcd e = _objmgr.get_node( n, id );

    //if(e)
    //    return e;

    //previous token was / or this 'if' isn't true => if's body may assume / preceding it
    if (tok.is_empty()  ||  Gpathtokenizer.in_group (tok[0], Gpathtokenizer.OPERATORS))
    {
        //return all childs
        //COID_LOCK(idc,"parse_path 1");
        return _objmgr.get_children( n, out );
    }
    //if (tok == '/')   return ersSYNTAX_ERROR;

    token t2 = tok;
    Gpathtokenizer.next(t2);

    if (tok == "..")
    {
        //COID_LOCK(idc,"parse_path 3");
        if( 0 != _objmgr.get_superior( n, &n ) )  return ersUNAVAILABLE;

        Gpathtokenizer.next (tok);
        if (tok.is_empty()  ||  Gpathtokenizer.in_group (tok[0], Gpathtokenizer.OPERATORS)) {
            *out.add(1) = n;
            return 0;
        }
        else if (tok != '/')
            return ersSYNTAX_ERROR;
        else {
            return parse_path( n, idh, tok, out, false );
        }
    }

    if (tok == '@') {
        if (t2.is_empty()  ||  Gpathtokenizer.in_group (t2[0], Gpathtokenizer.OPERATORS))
        {
            *out.add(1) = n;
            tok = t2;
            return 0;
        }
        else if (t2 == '/') {
            Gpathtokenizer.next (t2);
            return parse_path( n, idh, t2, out );
        }
    }

    if (tok == '@'  ||  Gpathtokenizer.in_group (tok[0], Gpathtokenizer.ALPHANUMERIC))
    {
        token name, cls;
        bool noclass = false;
        if( tok != '@' )
        {
            name = tok;
            Gpathtokenizer.next(tok);
            if( tok == '@' )
                Gpathtokenizer.next(tok);
            else if( tok == '/'  ||  t2.is_empty()  ||  Gpathtokenizer.in_group( t2[0], Gpathtokenizer.OPERATORS ) )
                noclass = true;
            else
                return ersSYNTAX_ERROR;
        }
        else {
            name.set_empty("");
            Gpathtokenizer.next (tok);
        }

        if(!noclass)
        {
            if( !Gpathtokenizer.in_group( tok[0], Gpathtokenizer.ALPHANUMERIC )  ||
                Gpathtokenizer.in_group( tok[0], Gpathtokenizer.NUMERIC ) )
                return ersSYNTAX_ERROR;
            cls = tok;
            Gpathtokenizer.next (tok);
        }
        else
            cls.set_empty("");

        uint root=0;
        bool f;
        if(do_scan)
        {
            root = n;
            NODEREF<CoidNode> r;
            if( 0 != _objmgr.get_node_of_class( n, cls, 0, r ) )
                f = 0 == _objmgr.scanto_node_of_class( n, root, cls, 0, &n );
            else
                f = true;
        }
        else
            f = 0 == _objmgr.goto_node_of_class( n, cls, 0, &n );

        if (!f)  return ersNOT_FOUND "class";

        bool isemp = false;

        if( tok.is_empty()  ||  Gpathtokenizer.in_group( tok[0], Gpathtokenizer.OPERATORS ) )
            isemp = true;
        else if (tok != '/')
            return ersSYNTAX_ERROR;

        token t2 = tok;
        for(;;)
        {
            token tn;
            if( name.is_empty()  ||  (0 == _objmgr.get_node_name( n, tn )  &&  name == tn) )
            {
                if (isemp)
                    *out.add(1) = n;
                else
                    parse_path( n, idh, t2, out, false );
            }

            if(do_scan)
                f = 0 == _objmgr.scanto_node_of_class( n, root, cls, 0, &n );
            else
                f = 0 == _objmgr.goto_node_of_class( n, cls, 0, &n );

            if(!f)  break;
        }

        return 0;
    }
    else if (tok == '/'  ||  tok == '*')
    {
        //COID_LOCK(idc,"parse_path 4");

        if( !_objmgr.goto_first( n, &n ) )  return 0;
        for(;;)
        {
            parse_path( n, idh, tok, out, false );
            if( !_objmgr.goto_next( n, &n ) )  break;
        }
        return 0;
    }
    //
    return ersSYNTAX_ERROR;
}

////////////////////////////////////////////////////////////////////////////////
/*
opcd ServerGlobal::gen_digraph (LNID from, txtstream& out) const
{
    COID_LOCK(from,"gen_digraph");

    out << "digraph coid_graph {\n\tnode [font = \"helvetica\",fontsize=\"8\"];\n";

    REF  root;
    if (!_tree.get_node (from, root)) return ersOUT_OF_RANGE "no such node";

    dynarray<uint> con;

    get_digraph_node (root, out, con);

    for (uint i=0; i<con.size(); ++i)
    {
        out << "\tsubgraph " << "cluster_" << i << " {\n";
        out << "\tcolor=purple;\n\tstyle=\"dashed\";\n\t";

        ConnectionCoid* bn = (ConnectionCoid*) get_node (con[i]);
        for (uint j=0; j<bn->_inst.size(); ++j)
        {
            if( bn->_inst[j]->_flags & ServiceInstanceCoid::fOWNER )
                out << 'n' << bn->_inst[j]->get_id() << "; ";
        }
        out << 'n' << con[i] << "\n\t}\n";
    }

    out << '}';

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd ServerGlobal::get_digraph_node (const REF& root, txtstream& dg, dynarray<uint>& con) const
{
    CoidNode::graph_attr attr;
    char buf[8];

    if (root->_class == ConnectionCoid::GET_INTERFACE())
    {
        *con.add (1) = root.get_node_id();
        ConnectionCoid* cn = (ConnectionCoid*) root->_node->bnode;
        for (uint i=0; i<cn->_inst.size(); ++i)
        {
            if (cn->_inst[i]._flags & bnode_connection::fNOT_OWNER)
                dg << "\tn" << cn->_inst[i]._node->get_id() << " -> n" << root.get_node_id()
                    << "[weight=\".2\",color=red];\n";
        }
    }

    root->_node->bnode->get_graph_attr (attr);
    dg << "\tn" << root.get_node_id() << " [label=\"" << root->_node->bnode->_name
        << '(' << root.get_node_id() << ")\\n["
        << root->_node->cmdin->class_name() << "]\",style=filled,color=\""
        << attr.get_color(buf) << "\"];\n";

    dynarray<COID>    refs;
    root->get_referred (refs);
    for (uint i=0; i<refs.size(); ++i)
    {
        dg << "\tn" << root.get_node_id() << " -> n" << refs[i].get_node_id()
            << " [style=dotted,weight=\".2\"];\n";
    }

    if (!root.is_lowest())
    {
        REF node = root;
        node.goto_first();
        for (;;)
        {
            node->_node->bnode->get_graph_attr (attr);
            dg << "\tn" << root.get_node_id() << " -> n" << node.get_node_id()
                << " [weight=\"" << attr._weight << "\"];\n";

            if (!node.goto_next())  break;
        }

        node = root;
        node.goto_first();
        for (;;)
        {
            get_digraph_node (node, dg, con);

            if (!node.goto_next())  break;
        }
    }

    return 0;
}
*/
} // namespace coid
